#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <assert.h>
#include <string.h>
#include <errno.h>


#define NUM 1024
#define OPT_NUM 64

#define NONE_REDIR    0
#define INPUT_REDIR   1
#define OUTPUT_REDIR  2
#define APPEND_REDIR  3
#define trimSpace(start) do{ while(isspace( *start )) ++start;}while(0)//只需要执行一次

int lastCode = 0;
int lastSig = 0;



char lineCommand[NUM];
char *myargv[OPT_NUM]; //指针数组

int redirType = NONE_REDIR;
char *redirFile = NULL;

//"ls -a -l > myfile.txt " > "ls -a -l " " myfile.txt "
void commandCheck(char *commands)
{
    assert( commands );
    char *start = commands;
    char *end = commands + strlen(commands);//不需要+1，因为此时end已经表示\0

    while( start < end )
    {
        if( *start == '>' )
        {
            *start = '\0';
            start++;
            if( *start == '>' )
            {
                //"ls -a >> file.txt"
                redirType = APPEND_REDIR;
                start++;
            }
            else
            {
                //"ls -a >   file.txt"
                redirType = OUTPUT_REDIR;
            }
            trimSpace(start);//去掉空格
            redirFile = start;
            break;
        }
        else if( *start == '<' )
        {
            //"cat < myfile.txt"
            *start = '\0';
            start++;
            trimSpace(start);//跳过空格 因为有这种情况 "cat <      myfile.txt" 此时需要跳过空格
            //填写重定向信息
            redirType = INPUT_REDIR;
            redirFile = start;
            break;
        }
        else
        {
            start++;
        }
    }
}

int main()
{
    while(1)
    {
    
     redirType = NONE_REDIR;
     redirFile = NULL;
     errno = 0;
    //输出提示符
    printf("用户名@主机名 当前路径#");
    fflush(stdout);
    
    //获取用户输入,输入的时候，输入\n
    char *s = fgets(lineCommand,sizeof(lineCommand)-1,stdin);
    assert( s != NULL );
    (void)s;
    //清除最后一个\n,abcd\n
    lineCommand[strlen(lineCommand) - 1]=0;//就是将输入的\n去除，这样就不会多一个空行
//    printf("test : %s\n",lineCommand);
      
    //"ls -a -l -i" -> "ls" "-a" "-l" "-i"
    //"ls -a -l -i > myfile.txt" -> "ls -a -l -i" "myfile.txt"
    //"ls -a -l -i >> myfile.txt " -> "ls -a -l -i" "myfile.txt"
    //"cat < myfile.txt " -> "cat" "myfile.txt"
    commandCheck(lineCommand);

    //字符串切割
    myargv[0]=strtok(lineCommand," ");
    //如果没有子串了，strtok -> NULL, myargv[end]=NULL 
    int i = 1;
    if(myargv[0] != NULL && strcmp(myargv[0],"ls")==0)
    {
        myargv[ i++ ] = (char*)"--color=auto";
    }
    while( myargv[i++] =strtok(NULL ," ") );
    
    // 如果是cd命令，不需要创建子进程，让shell自己执行对应的命令，本质就是执行系统接口
    // 像这种不需要我们子进程来执行，而是让shell自己执行的命令 --- 叫做内建或者内置命令
    if(myargv[0] != NULL && strcmp(myargv[0] , "cd") == 0)
    {
        if(myargv[1] != NULL)
            chdir(myargv[1]);
        continue;
    }
    if(myargv[0] != NULL && myargv[1] != NULL && strcmp(myargv[0] , "echo") == 0)
    {
        if(strcmp(myargv[1],"$?") == 0)
        {
            printf("%d,%d\n",lastCode,lastSig);
        }
        else
        {
            printf("%s\n",myargv[1]);
        }

            continue;
    }
    //测试是否成功，条件编译
#ifdef DEBUG 
    for(int i=0; myargv[i]; i++)
    {
        printf("myargv[%d]: %s\n",i,myargv[i]);
    }
#endif
    //执行命令
    pid_t id = fork();
    assert( id != -1 );
    if(id == 0)
    {
      // 因为命令是子进程执行的，真正重定向的工作一定是子进程完成的
      // 如何重定向，是父进程要给子进程提供信息的
      // 这里的重定向会影响父进程吗？不影响,进程据有独立性
     int fd = 0; 
        switch(redirType)
        {
            case NONE_REDIR:
                // 什么都不会做
                break;
            case INPUT_REDIR:
                {
                    fd = open(redirFile,O_RDONLY);
                    if( fd < 0 )
                    {
                        perror("open");
                        exit(errno);
                    }
                    //重定向文件已经成功打开了
                    dup2(fd,0);
                }
                break;
            case OUTPUT_REDIR:
            case APPEND_REDIR:
                //这种写法，输出重定向和追加重定向都会使用这段程序
                umask(0);
                int flags = O_WRONLY | O_CREAT;
                if( redirType == APPEND_REDIR )
                {
                    flags |= O_APPEND;
                }
                else
                {
                    flags |= O_TRUNC;
                }
                fd = open(redirFile, flags, 0666);
                if( fd < 0 )//说明打开文件失败
                {
                    perror("open");
                    exit(errno);
                }
                //重定向文件已经成功打开了
                dup2(fd,1);
                break;
            default:
                printf("bug?\n");
                break;
        }
        execvp(myargv[0],myargv);// 执行程序替换的时候，会不会影响曾经进程打开的重定向文件？ 不会
        exit(1);
    }
    int status = 0;
    pid_t ret = waitpid(id, &status, 0);
    assert( ret > 0 );
    (void)ret;
    lastCode = ((status >> 8) & 0xFF);
    lastSig = (status & 0x7F);

}
    return 0;
}
